#pragma once

#include "OrionFlames.h"
#include "xForm.h"
#include "VariationList.h"
#include "GlutClone.h"

namespace OrionFlames
{
class OFAPI Preset
{
public:
	Preset(string name, float screenInitVal, float screenInitScale, float exposure, float colorful, float texScale)
	{
		Init(name, screenInitVal, screenInitScale, exposure, colorful, texScale);
	}

	Preset(string name, double screenInitVal, double screenInitScale, double exposure, double colorful, double texScale)
	{
		Init(name, (float)screenInitVal, (float)screenInitScale, (float)exposure, (float)colorful, (float)texScale);
	}

	Preset(const Preset& preset)
	{
		m_FirstLevel = preset.m_FirstLevel;
		m_LastLevel = preset.m_LastLevel;//Last multigrid level to display (==output resolution).
		m_Iterations = preset.m_Iterations;//Per multigrid level.
		m_ScreenInitVal = preset.m_ScreenInitVal;
		m_ScreenInitScale = preset.m_ScreenInitScale;
		m_Exposure = preset.m_Exposure;
		m_Colorful = preset.m_Colorful;
		m_TexScale = preset.m_TexScale;
		m_TexScalei = preset.m_TexScalei;
		m_MapBias = preset.m_MapBias;
		m_Name = preset.m_Name;
		m_PixelShaderText = preset.m_PixelShaderText;
		m_VertexShaderText = preset.m_VertexShaderText;

		//Skip statics, add later if we make them non static.

		//Clear transforms, iterate through and assign.
		m_Transforms.clear();
		for (unsigned int i = 0; i < preset.m_Transforms.size(); i++)
			m_Transforms.push_back(preset.m_Transforms[i]);
	}

	float TexScale() { return m_TexScale; }
	void TexScale(float texScale) { m_TexScale = texScale; m_TexScalei = (float)(1.0 / texScale); }
	float TexScalei() { return m_TexScalei; }
	float Colorful() { return m_Colorful; }
	void Colorful(float colorful) { m_Colorful = colorful; }
	float Exposure() { return m_Exposure; }
	void Exposure(float exposure) { m_Exposure = exposure; }
	float ScreenInitScale() { return m_ScreenInitScale; }
	void ScreenInitScale(float initScale) { m_ScreenInitScale = initScale; }
	int FirstLevel() { return m_FirstLevel; }
	void FirstLevel(int firstLevel) { m_FirstLevel = firstLevel; }
	int LastLevel() { return m_LastLevel; }
	void LastLevel(int lastLevel) { m_LastLevel = lastLevel; }
	int Iterations() { return m_Iterations; }
	void Iterations(int iterations) { m_Iterations = iterations; }
	bool Empty() { return m_Transforms.empty(); }
	unsigned int Size() { return m_Transforms.size(); }
	string& Name() { return m_Name; }//This must return a reference or else managed code will crash when calling.
	string& PixelShaderText() { return m_PixelShaderText; }
	xForm& Transform(unsigned int index) { return m_Transforms.at(index); }//at() should return a reference and not a copy. Is [] ok? - Matt.

	void PushBack(xForm& xform)
	{
		m_Transforms.push_back(xform);
		UpdateTags();
		//Perhaps merge base params when adding via specified percentage.
	}

	xForm PopFront()
	{
		xForm front;
		
		if (!m_Transforms.empty())
		{
			front = xForm(m_Transforms.front());//Like doing a pop front.
			m_Transforms.erase(m_Transforms.begin());
			UpdateTags();
		}

		return front;
	}

	void Clear()
	{
		while(!Empty())
			PopFront();
	}

private:
	void Init(string name, float screenInitVal, float screenInitScale, float exposure, float colorful, float texScale)
	{
		m_ScreenInitVal = screenInitVal;
		m_ScreenInitScale = screenInitScale;
		m_Exposure = exposure;
		m_Colorful = colorful;
		m_TexScale = texScale;
		m_FirstLevel = 5;
		m_LastLevel = 11;//These seem to give good values. 12 is a bit jittery on my card. Nicer cards could probably go higher.
		m_Iterations = 3;
		m_MapBias = 0.0;
		m_TexScalei = (float)(1.0 / m_TexScale);
		m_Name = name;
	}

	void UpdateTags()
	{
		for (unsigned int i = 0; i < m_Transforms.size(); i++)
		{
			char ch[10];
			sprintf_s(ch, 10, "%d", i);
			m_Transforms[i].Tag(string(ch));
		}
	}

public:
	//Return the color of IFS map m.
	//This coloring function is totally arbitrary and doesn't follow anything
	//along the lines of what Draves and everyone else have done. Need to get rid
	//of this and do what they do.//Matt
	//This is the weirdest color algorithm I've ever seen in my life. In order to make the color a certain component, instead of
	//setting it to one, the closer you set it zero, the more color of that component it gets.
	//Also, even if we test by setting everything to red, some presets come out turquoise.
	vec4 MapColor(int m)
	{
#if 1
		float r = 1.0, g = 1.0, b = 1.0;
		
		switch (m % 3)
		{
			case 0: r -= m_Colorful; break;
			case 1: g -= m_Colorful; break;
			case 2: b -= m_Colorful; break;
		}

		if (m_Exposure < 0.0)
		{
			std::swap(r, g);//Reddish, not greenish.
			std::swap(b, g);//Purplish, not greenish.
		}

		float e = fabs(m_Exposure) * m_Transforms[m].Weight();//Linear weight. The only place this is used. Really need to get rid of this.
		//glColor3f(e*r,e*g,e*b);
		return vec4(e * r, e * g, e * b, 1.0);
#else

		//Dummy test that doesn't really work.
		vec4 v;
		v.w = 1.0;

		switch (m % 3)
		{
			case 0 :
				{
					v.x = 0.0;
					v.y = 1.0;
					v.z = 1.0;
				}
				break;
			case 1 : 
				{
					v.x = 1.0;
					v.y = 0.0;
					v.z = 1.0;
				}
				break;
			case 2 :
				{
					v.x = 1.0;
					v.y = 1.0;
					v.z = 0.0;
				}
				break;
		}

		return v;
#endif
	}

	//Run one step of the simulation for all the per-pixel maps for the current IFS.
	void SimulatePerpixelMaps(int level, bool createNewProgram)//Make this a member function since it needs to access the transforms so much. Make sure it's ok later. - Matt.
	{
		if (createNewProgram)
			m_Prog = MakeProgramObject();//This only needs to be done when the transform changes to a new one. Perhaps move outside or put a check here. Prog may even be moved out since logic of what's changed is more global/container level - Matt.

		glUseProgramObjectARB(m_Prog);
		//Tex scale seems to be working differently here than in the original.
		//Here it zooms, in the original it just make the image look funny. Which is right?//Matt
		glUniformTextures(m_Prog, m_TexScale, m_TexScalei)

		//Set uniform variables for each map.
		for (unsigned int m = 0; m < m_Transforms.size(); m++)
		{
			m_Transforms[m].SetUniforms(m_Prog);
			//This makes the color different for each map. Would they ever be the same? Can they vary in other ways?
			//Perhaps each map gets an assigned color, or range of colors?
			glUniform4fvARB(glGetUniformLocationARB(m_Prog, string("color" + m_Transforms[m].Tag()).c_str()), 1, MapColor(m));
		}

		//Draw screen-filling quad.
		//Instead of drawing here, we ideally want to return just the texture so the
		//user can do whatever they want with it.
		DrawTexture(mat4(1.0), vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 0.5));
		glUseProgramObjectARB(0);
	}

	//Run a multigrid simulation of the current IFS, and return the finished texture.
	GLuint SimulateIFS(bool createNewProgram) 
	{
		glMatrixMode(GL_PROJECTION);//Clear out projection matrix.
		glLoadIdentity();//Reset matrix at start of frame.
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	
		//Framebuffer output textures.
		//We really need multigrid to get decent convergence at framerate.
		//static GLuint texLevels[nlevel+1][2]={{0}};//Master copy.
		//GLuint frameSrc=0, frameDest;//Working copy, pingponged buffers.

		//Build a texture for every possible multigrid level.
		if (m_TexLevels[0][0]==0) 
		{  
			glGenTextures(2 * (m_NLevel + 1), &m_TexLevels[0][0]);//Make texture handles.
			
			for (int level = 0; level <= m_NLevel; level++)
			{
				for (int i = 0; i < 2; i++)
				{
					glBindTexture(GL_TEXTURE_2D, m_TexLevels[level][i]);
					glTexImage2D(GL_TEXTURE_2D, 0,
						GL_RGBA8, 1 << level, 1 << level,//Data format and size (pixels).
						0, GL_LUMINANCE, GL_FLOAT, 0);
					glGenerateMipmapEXT(GL_TEXTURE_2D);//<- Needed on many cards!
					oglTexWorkright();
				}
			}
		}
	
		//Framebuffer object.  Only one needed, with attachments swapped around.
		//m_FrameBuffer = 0;//Figure out if this should be instance or static var and how it should be initialized. - Matt
		if (m_FrameBuffer == 0)//Create up framebuffer object--set it up below.
			glGenFramebuffersEXT(1, &m_FrameBuffer);
	
		//Render into our framebuffer object.
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FrameBuffer);
		glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_ONE);//Pure additive alpha.
		glDisable(GL_CULL_FACE);//Some maps produce backwards triangles.

		glScalef((float)1.0, (float)1.0, (float)0.01);//Scale down Z axis, to avoid z-clipping.

		glScalef(2.0, 2.0, 1.0);//Center X and Y on 0.5,0.5, like texture coordinates.
		glTranslatef(-0.5, -0.5, 0.0);
	
		//Loop over multigrid levels.
		for (int level = m_FirstLevel; level <= m_LastLevel; level++)
		{
			//When starting a new multigrid level, just swap the destination,
			//using the old source texture.  Texcoords will automatically interpolate!
			m_FrameDest = m_TexLevels[level][0];
			if (level == m_FirstLevel)//Need a source too.
				m_FrameSource = m_TexLevels[level][1];
			
			int w = 1 << level;//Pixels on this level (dest).
			int h = 1 << level;
	
			//Iteratively draw the IFS maps.
			for (int depth = 0; depth <= m_Iterations; depth++)
			{
				//Make sure we're rendering into frameTex[1].
				glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
					GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
					m_FrameDest, 0);

				//Make sure the framebuffer's happy.
				GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
				if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
					BadFramebuffer(status);

				glDisable(GL_DEPTH_TEST);//Or else attach to GL_DEPTH_ATTACHMENT_EXT!
				glViewport(0, 0, w, h);
				glScissor(0, 0, w, h);
		
				//Clear the screen.
				if (depth == 0 && level == m_FirstLevel) 
				{
					//Starting a new computation-- total reset.
					float g = m_ScreenInitVal;//Initialize color to this.
					int drawWhat = 1;
					if (drawWhat != 0)
						g = 0.0;//Black background.
			
					glClearColor(g, g, g, 1.0f); 
					glClear(GL_COLOR_BUFFER_BIT);
			
					glDisable(GL_TEXTURE_2D);
					glPushMatrix();
					glTranslatef(0.5, 0.5, 0.0);

					//Screen init scale doesn't really do much beyond a value of two. It just gives us the size of the cube we're going to draw on. Revisit later.//Matt
					float size = m_ScreenInitScale * (CoordMap::PlaneToTex(1.0, m_TexScale) - CoordMap::PlaneToTex(0.0, m_TexScale));//Estimate unit-plane size in texture.
					glColor3f(m_ScreenInitVal, m_ScreenInitVal, m_ScreenInitVal);
					
					//glutSolidTetrahedron();//Looks cool for some.
					//glutSolidCylinder(size, 10, 20, 5);
					//glutSolidIcosahedron();
					
					//Instead of drawing here, we ideally want to return just the texture so the
					//user can do whatever they want with it.
					switch (drawWhat)//figure out what the hell this is for - Matt.
					{
						case 1: GlutClone::GlutSolidCube(size); break;
						case 2: GlutClone::GlutSolidSphere(size, 10, 20); break;
					};

					glPopMatrix();
					glColor3f(1, 1, 1);
			
					glFinish();
			
					//Build mipmaps of our freshly-rendered data.
					glBindTexture(GL_TEXTURE_2D, m_FrameDest);
					glEnable(GL_TEXTURE_2D);
					glGenerateMipmapEXT(GL_TEXTURE_2D); 
		
					std::swap(m_FrameSource, m_FrameDest);
					continue;//Go around again to draw maps.
				} 
				//Else not the first frame--we'll erase during the per-pixel inverse.
		
				//Draw our IFS estimate frameSrc, transformed by each map.
				glBindTexture(GL_TEXTURE_2D, m_FrameSource);
				glEnable(GL_TEXTURE_2D);
		
				/////////////// Per-pixel inverse functions ///////////////
				glDisable(GL_BLEND);//Avoids erase--hard-overwrite previous texture.
				//Only create a new program if:
				//1) The caller of this function passed in true, in response to a preset change.
				//2) We are on the first level.
				//3) We are on the second iteration of depth (we did a continue on depth == 0).
				SimulatePerpixelMaps(level, createNewProgram && level == m_FirstLevel && depth == 1);
				
				/////////////// If no inverse function, approximate the inverse using geometry ///////////////
				glEnable(GL_BLEND);//Need to combine per-pixel with geometry (FIXME: log textures?).

				//Build mipmaps of our freshly-rendered data.
				glBindTexture(GL_TEXTURE_2D, m_FrameDest);
				glGenerateMipmapEXT(GL_TEXTURE_2D); 
		
				//Swap textures zero and one.
				std::swap(m_FrameSource, m_FrameDest);
				if (depth == 0)//Done with first pass through a new multigrid level--now update other buffer.
					m_FrameDest = m_TexLevels[level][1];
			}//End multipass depth loop.
		}//End multigrid loop.

		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	
		return m_FrameSource;//We flipped src around to dest.
	}

	/* Bind in an offscreen framebuffer object with this size */
	//Appears to only be used for saving, omit for now - Matt.
	//void bind_offscreen_framebuffer(int w,int h)
	//{
	//	int fb=0;
	//	static GLuint texLevels[nlevel+1][2]={{0}}; /* master copy */
	//	GLuint frameDest=0; /* texture */
	//	glGenTextures(1,&frameDest); /* make texture handles */
	//	glBindTexture(GL_TEXTURE_2D,frameDest);
	//	glTexImage2D(GL_TEXTURE_2D,0,
	//		GL_RGBA8,w,h,0,GL_RGBA,GL_FLOAT,0);
	//	glGenerateMipmapEXT(GL_TEXTURE_2D); /*<- needed on many cards! */
	//
	//	/* Framebuffer object.  Only one needed, with attachments swapped around. */
	//	static GLuint fbo=0;
	//	if (fbo==0) { /* create up framebuffer object--set it up below */
	//		glGenFramebuffersEXT(1,&fbo);
	//	}
	//	/* Render into our framebuffer object */
	//	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fbo);
	//	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
	//		GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
	//		frameDest, 0);
	//	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	//	if (status!=GL_FRAMEBUFFER_COMPLETE_EXT)
	//		bad_framebuffer(status);
	//}

protected:

	string CreateSumFunctionsString()
	{
		string sumFunctions;
		sumFunctions.reserve(m_Transforms.size() * 80);

		for (unsigned int m = 0; m < m_Transforms.size(); m++)
		{
			std::string tag = m_Transforms[m].Tag();
			sumFunctions +=
				"	sum += color" + tag + " * nonlinear_inverse" + tag + "(p);\n";
				//"	sum += color" + tag + " + nonlinear_inverse" + tag + "(p) / 2.0;\n";
		}

		return sumFunctions;
	}

	string CreateInverseFunctionsString()
	{
		string inverseFunctions;
		inverseFunctions.reserve(m_Transforms.size() * 1024);

		for (unsigned int m = 0; m < m_Transforms.size(); m++)
		{
			std::string tag = m_Transforms[m].Tag();

			//This seems redundant. We could probably just have one global g() function, instead of g0, g1, g2 etc which all have identical code.
			//Also, g() is meaningless. A better name would be unpack().//Matt
			inverseFunctions +=
			"//------------------- w" + tag + " - " + m_Transforms[m].Var()->getName() + " -----------------\n"
			"uniform vec4 color" + tag + ";\n\n"
			"vec4 g" + tag + "(vec2 p)\n" +
			"{\n" +
			"	return exp2(texture2D(tex, TXfmPL(p)) * 20.0) - 1.0;\n" +
			"}\n\n" +
			m_Transforms[m].GetInverseCode();// + "\n";
		}

		return inverseFunctions;
	}

	string MakeVertexShader()
	{
		string s = "// GLSL Vertex shader (trivial).\n";
		s += "varying vec2 destcoords;\n";
		s += "void main(void)\n";
		s += "{\n";
		s += "	destcoords = vec2(gl_Vertex);\n";
		s += "	gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n";
		s += "}\n";

		return s;
	}

	string MakeFragmentShader()
	{
		string fragment;
		fragment.reserve(4096);
		string sumFunctions = CreateSumFunctionsString();
		string inverseFunctions = CreateInverseFunctionsString();

		// GLSL Fragment shader
		fragment += "varying vec2 destcoords;\n";
		fragment += "uniform sampler2D tex;\n";
		fragment += CoordMap::GLSLTexPlaneString() + "\n" + inverseFunctions;
		fragment += "//Combined inverse-sampling function.\n";
		fragment += "vec4 sumInverses(vec2 p)\n";
		fragment += "{\n";
		fragment += "	vec4 sum = vec4(0.0);\n";
		fragment += 	sumFunctions + "\n";
		fragment += "	return log2(sum + 1.0) * (1.0 / 20.0);\n";
		fragment += "}\n\n";

		//Really should avoid using STRINGIFY unless we absolutely have to because it doesn't observe newlines
		//and make the resulting shader code impossible to read. So instead, just compose it like a normal string above.//Matt.
		fragment += "//GLSL Fragment shader main.\n";
		fragment += "void main(void)\n";
		fragment += "{\n";
		fragment += "	gl_FragColor = sumInverses(PLfmTX(destcoords));\n";
		//fragment += "	//gl_FragColor = vec4(destcoords.x,destcoords.y,0.5,0.5);\n";//Debug in blue.
		//fragment += "	//gl_FragColor.a*=0.93;\n";//Only be opaque where jacobi is crushing in.
		fragment += "}\n";

		return fragment;
	}

	GLhandleARB MakeProgramObject()
	{
		//Create the text for the shaders and save them to member vars in case debug output is needed elsewhere.
		m_PixelShaderText = MakeFragmentShader();
		m_VertexShaderText = MakeVertexShader();
		//Compile the shaders.
		return makeProgramObject(m_VertexShaderText.c_str(), m_PixelShaderText.c_str());
	}

	int m_FirstLevel;//First multigrid level to compute.
	int m_LastLevel;//Last multigrid level to display (==output resolution).
	int m_Iterations;//Per multigrid level.
	float m_ScreenInitVal;
	float m_ScreenInitScale;
	float m_Exposure;
	float m_Colorful;
	float m_TexScale;
	float m_TexScalei;
	float m_MapBias;
	GLuint m_FrameSource;//Working copy, pingponged buffers.
	GLuint m_FrameDest;
	string m_Name;
	string m_PixelShaderText;
	string m_VertexShaderText;
	/*vector*/deque<xForm> m_Transforms;
	static GLhandleARB m_Prog;//Saved GLSL program for all per-pixel maps.
	static GLuint m_FrameBuffer;
	static const int m_NLevel = 12;//Original was 13, but that kept giving us problems - Matt;//Highest resolution multigrid texture is 2^nlevel pixels.
	static GLuint m_TexLevels[m_NLevel + 1][2];//Master copy.
};
}